classdef MotionCollection < handle
	properties
	motions = struct();  % hashtable-like
	nCams;
	rotateZObj;
	end

	methods
	function obj = MotionCollection(root, nCams, varargin)
		p = inputParser;
		addRequired(p, 'root', @(x) exist(x, 'file') == 7);
		addRequired(p, 'nCams', @isnumeric);
		addParamValue(p, 'onlyValidFrames', false, @islogical);
		addParamValue(p, 'partitionStr', 'exp', @ischar);
		parse(p, root, nCams, varargin{:});

		root = p.Results.root;
		onlyValidFrames = p.Results.onlyValidFrames;

		matFiles = retrieveFiles(root, [p.Results.partitionStr '.mat'], 3);
		% e.g. 'HumanEva\S2\Mocap_Data\Jog_3.exp.mat'
		pattern = ['(?<subject>S\d)\\Mocap_Data', ...
			'\\(?<action_trial>[\w]+).' p.Results.partitionStr '.mat'];

		% For each .mat file, sets the corresponding fields in 
		% the `motions` struct.
		for i = 1:length(matFiles)
			mFile = matFiles{i};
			mFilePath = fullfile(root, mFile);
			load(mFilePath);  % var `coordinates` & `frameNo`
			packed = struct('coordinates', coordinates, 'frameNo', frameNo);

			if onlyValidFrames
				% Maps from each index in valid poses to that in all poses.
				% e.g. [1 0 0 2 3]' -> [1 4 5]'
				% `+ 1` because subscript indices cannot be 0.
				validToAll = accumarray(packed.frameNo + 1, ...
					(1:length(packed.frameNo))', ...
						[size(packed.coordinates, 1), 1]);
				packed.frameNo = validToAll(2:end);
			end

			matched = regexp(mFile, pattern, 'names');
			% e.g. motions.S2.Jog_3 = packed struct
			obj.motions.(matched.subject).(matched.action_trial) = packed;
		end

		obj.nCams = p.Results.nCams;
		obj.rotateZObj = Rotator('Z', nCams);  % up: Z axis
	end

	function pose = getPoseCoord(obj, subject, actionTrial, camID, ...
		frameID)
	% Gets the coordinate of the ground truth pose.
	% Output:
	%   pose: 1-by-(nJoints*3) vector of pose coordinate.
		packed = obj.motions.(subject).(actionTrial);

		% [1 0 0 2 3](4) = 2, for example
		storedIndex = packed.frameNo(frameID);
		if storedIndex == 0
			pose = [];  % an invalid frame
			return
		end

		pose = packed.coordinates(storedIndex, :);
		pose = obj.rotateZObj.rotate1(pose, camID);
	end

	function howMany = nValidMocapFrames(obj, verbose)
		if nargin < 2
			verbose = false
		end

		howMany = 0;
		subjects = obj.motions;
		for subject = fieldnames(subjects)'
			actions = subjects.(subject{1});
			for action = fieldnames(actions)'
				nFrames = size(actions.(action{1}).coordinates, 1);
				if verbose
					fprintf('%s - %s: %d\n', subject{1}, action{1}, nFrames);
				end
				howMany = howMany + nFrames;
			end
		end
	end
	end
end